package svg;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Stack;

import util.Petal;
import util.SoundManager;
import util.Star;

/**
 * The game controller contains all the intelligence which is needed for the
 * game.
 */
public class GameController
{

	private MainWindow _window;
	private Star[] _places;
	private Petal[] _petals;
	private Star _preview;
	private Stack<Star> _starStack;
	private Map<Integer, Integer[]> _upMap;
	private Map<Integer, Integer[]> _vertMap;
	private Map<Integer, Integer[]> _downMap;
	private BoardAdapter _boardAdapter;
	private int[] _ups = new int[] { 2, 6, 7 };
	private int[] _verts = new int[] { 3, 4, 8 };
	private int[] _downs = new int[] { 1, 5, 9 };
	private int _score;

	/** Contains all indices of the up lines. */
	private int[][] _upLines = new int[][] { { 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11, 12 }, { 13, 14, 15, 16 }, { 17, 18, 19 } };
	/** Contains all indices of the vertical lines. */
	private int[][] _vertLines = new int[][] { { 1, 4, 8 }, { 2, 5, 9, 13 }, { 3, 6, 10, 14, 17 }, { 7, 11, 15, 18 }, { 12, 16, 19 } };
	/** Contains all indices of the down lines. */
	private int[][] _downLines = new int[][] { { 8, 13, 17 }, { 4, 9, 14, 18 }, { 1, 5, 10, 15, 19 }, { 2, 6, 11, 16 }, { 3, 7, 12 } };

	private SoundManager _soundManager = null;
	
	/**
	 * Creates a new instance of the game controller.
	 * 
	 * @param window
	 *            the window which displays the gui
	 */
	public GameController(MainWindow window)
	{
		_window = window;
		_boardAdapter = _window.getBoardAdapter();
		_window.setGameController(this);
		initialize();
	}

	/**
	 * Initializes all needed objects.
	 */
	private void initialize()
	{
		_upMap = new HashMap<Integer, Integer[]>();
		_vertMap = new HashMap<Integer, Integer[]>();
		_downMap = new HashMap<Integer, Integer[]>();

		_upMap.put(new Integer(1), new Integer[] { 2, 25 });
		_upMap.put(new Integer(2), new Integer[] { 4, 23 });
		_upMap.put(new Integer(3), new Integer[] { 6, 21 });
		_upMap.put(new Integer(4), new Integer[] { 8, 19 });
		_upMap.put(new Integer(5), new Integer[] { 10, 17 });

		_vertMap.put(new Integer(1), new Integer[] { 20, 27 });
		_vertMap.put(new Integer(2), new Integer[] { 18, 29 });
		_vertMap.put(new Integer(3), new Integer[] { 1, 16 });
		_vertMap.put(new Integer(4), new Integer[] { 3, 14 });
		_vertMap.put(new Integer(5), new Integer[] { 5, 12 });

		_downMap.put(new Integer(1), new Integer[] { 15, 22 });
		_downMap.put(new Integer(2), new Integer[] { 13, 24 });
		_downMap.put(new Integer(3), new Integer[] { 11, 26 });
		_downMap.put(new Integer(4), new Integer[] { 9, 28 });
		_downMap.put(new Integer(5), new Integer[] { 7, 30 });

		_soundManager = new SoundManager();
	}

	public SoundManager getSoundManager()
	{
		return _soundManager;
	}
	
	/**
	 * Starts the game by creating the stars and putting the first one on the
	 * preview field.
	 */
	public void startGame()
	{
		_places = new Star[19];
		_petals = new Petal[30];
		_starStack = new Stack<Star>();

		createStars();
		popNextStar();
	}

	/**
	 * Sets the next available star to the preview field. Calculates the actual
	 * score and sends it to the board.
	 */
	private void popNextStar()
	{
		_preview = _starStack.pop();
		_boardAdapter.setPreviewStar(_preview);
	}

	/**
	 * Checks if there are less than 8 stars left. Than the game is finished.
	 * Final score will be displayed on the board. Resets the preview star field.
	 */
	private void checkIfGameIsFinished()
	{
		if (_starStack.size() < 8)
		{
			// game is over
			_boardAdapter.setFinalScore(_score);
			// set empty preview star
			_boardAdapter.setPreviewStar(new Star());
		}
	}

	/**
	 * Updates the score view on the board.
	 */
	private void updateScore()
	{
		_score = calculateLinesScore(-1);
		_score += calculateLinesScore(0);
		_score += calculateLinesScore(1);
		_boardAdapter.setScore(_score);
	}

	/**
	 * This method returns the lines array for the given direction.
	 * 
	 * @param direction
	 *            -1: down, 0: vertical, 1: up
	 * @return the lines array
	 */
	private int[][] getLines(int direction)
	{
		switch (direction)
		{
		case -1:
			return _downLines;
		case 0:
			return _vertLines;
		case 1:
			return _upLines;
		default:
			throw new IllegalArgumentException("Star direction must be -1, 0 or 1. Not " + direction);
		}
	}

	/**
	 * This method returns the petal map for the given direction.
	 * 
	 * @param direction
	 *            -1: down, 0: vertical, 1: up
	 * @return the petal map
	 */
	private Map<Integer, Integer[]> getPetalMap(int direction)
	{
		switch (direction)
		{
		case -1:
			return _downMap;
		case 0:
			return _vertMap;
		case 1:
			return _upMap;
		default:
			throw new IllegalArgumentException("Star direction must be -1, 0 or 1. Not " + direction);
		}
	}

	/**
	 * This method calculates the sum of all lines.
	 * 
	 * @param the
	 *            direction -1: down, 0: vertical, 1: up
	 * @return the sum of all lines
	 */
	private int calculateLinesScore(int direction)
	{
		int score = 0;
		boolean lineComplete;
		// loop over each line
		for (int i = 0; i < getLines(direction).length; i++)
		{
			lineComplete = true;
			Star[] stars = new Star[getLines(direction)[i].length];
			// loop over each star in the line
			for (int j = 0; j < getLines(direction)[i].length; j++)
			{
				stars[j] = _places[getLines(direction)[i][j] - 1];
				// break if line is not yet completed
				if (stars[j] == null)
				{
					lineComplete = false;
					break;
				}
			}
			// check row if line is complete
			if (lineComplete)
			{
				int sum = checkRow(direction, stars);
				if (sum != 0)
				{
					// made a new line in this turn
					Integer[] petals = getPetalMap(direction).get(i + 1);
					Petal petal = new Petal(stars[0].getColorByDirection(direction));
					if (_petals[petals[0] - 1] == null)
					{
						_boardAdapter.setPetal(petal, petals[0]);
						_petals[petals[0] - 1] = petal;
						_boardAdapter.setPetal(petal, petals[1]);
						_petals[petals[1] - 1] = petal;

						// play a sound
						_soundManager.lineFull();
					}

				}
				score += sum;
			}
		}
		return score;
	}

	/**
	 * This method checks if the given stars got the same color in the given
	 * direction
	 * 
	 * @param direction
	 *            1: up, 0:vertical, -1:down
	 * @param stars
	 *            all stars that should be compared
	 * @return the score of this row, may be 0 if the stars don't match
	 */
	private int checkRow(int direction, Star... stars)
	{
		int scoreSum = 0;
		int[] score = new int[stars.length];
		for (int i = 0; i < stars.length; i++)
		{
			switch (direction)
			{
			case -1:
				score[i] = stars[i].getDown();
				break;
			case 0:
				score[i] = stars[i].getVertical();
				break;
			case 1:
				score[i] = stars[i].getUp();
				break;
			default:
				throw new IllegalArgumentException("Star direction must be -1, 0 or 1. Not " + direction);
			}
		}
		if (entriesAreEqual(score))
		{
			// calculate score
			scoreSum = stars.length * score[0];
		}
		return scoreSum;
	}

	/**
	 * Checks if all entries of the given array are equal.
	 * 
	 * @param array
	 *            any array with int values
	 * @return true if all entries contain the same value
	 */
	private boolean entriesAreEqual(int[] array)
	{
		for (int j = 1; j < array.length; j++)
		{
			if (array[0] != array[j])
			{
				return false;
			}
		}
		return true;
	}

	/**
	 * Creates and shuffles the stars which are needed for one game.
	 */
	private void createStars()
	{
		for (int up : _ups)
		{
			for (int vert : _verts)
			{
				for (int down : _downs)
				{
					_starStack.add(new Star(up, vert, down));
				}
			}
		}
		long seed = System.nanoTime();
		Collections.shuffle(_starStack, new Random(seed));
	}

	/**
	 * Displays all stars on the console. Developer feature.
	 */
	@SuppressWarnings("unused")
	private void printStars()
	{
		for (int i = 0; i < _starStack.size(); i++)
		{
			System.out.println(_starStack.get(i).toString());
		}
	}


	/**
	 * Clicks the star at the given position on the field. If the star was not
	 * clicked before, the preview star will be placed there and the next random
	 * star will be placed on the preview field. Game score will be calculated
	 * and updated. A check will be done to verify if the game has finished.
	 * 
	 * @param position the position of the star (0 to 19)
	 */
	public void clickStar(int position)
	{
		// only if there has not already been one
		if (_places[position - 1] == null)
		{
			_soundManager.starPlaced();
			_places[position - 1] = _preview;
			_boardAdapter.setStar(_preview, position);
			popNextStar();
			updateScore();
			checkIfGameIsFinished();
		}
	}
	
	public void clickRetry()
	{
		// play the game again
		_boardAdapter.setSplashScreenVisible(false);
		_boardAdapter.eraseAllStars();
		_boardAdapter.eraseAllPetals();
		_boardAdapter.setScore(0);
		startGame();
	}

}
